use std::fmt::{Debug, Display};

use crate::IResult;

use super::u2_int;



pub const SECS_PER_MINUTE: i32 = 60;
pub const SECS_PER_HOUR: i32 = 3600;
pub const SECS_PER_DAY: i32 = 86400;
pub const MINUTE_PER_HOUR: i32 = 60;

pub const HOURS_PER_DAY: i32 = 24;

pub const SECS_MIN: i32 = 0;
pub const SECS_MAX: i32 = SECS_PER_DAY - 1;


/// Secs in one day, 
/// h: hour
/// m: minute
/// s: second
#[derive(serde::Deserialize, serde::Serialize, Clone, Copy, Default, PartialEq, Eq)]
pub struct SecsOneDay {
    /// secs from 00:00:00
    secs: i32,
}


impl SecsOneDay {
    /// if secs_day is not in range [0, 86399](86400 - 1), secs_day will be adjust by adding 86400 or -86400 until fix.
    /// SECS_PER_DAY = 86400.
    pub fn from_secs_unsafe(secs_day: i32) -> Self {
        if 0 <= secs_day && secs_day <= SECS_MAX {
            Self { secs: secs_day }
        } else {
            let (_days, secs) = u2_int::divide_remain_i32(secs_day, SECS_PER_DAY);
            Self { secs }
        }

    }
    /// if secs_day is not in range [0, 86399](86400 - 1), secs_day will be adjust by adding 86400 or -86400 until fix.
    /// SECS_PER_DAY = 86400.
    pub fn from_secs_i64_unsafe(secs_day: i64) -> Self {
        if 0 <= secs_day && secs_day <= SECS_MAX as i64 {
            Self { secs: secs_day as i32 }
        } else {
            let (_days, secs) = u2_int::divide_remain_i64(secs_day, SECS_PER_DAY as i64);
            Self { secs: secs as i32 }
        }
    }

    pub fn min() -> Self {
        Self { secs: SECS_MIN }
    }
    pub fn max() -> Self {
        Self { secs: SECS_MAX }
    }

    pub fn set_min(&mut self) {
        self.secs = SECS_MIN;
    }
}


impl SecsOneDay {
    pub fn from_hms(hour: u8, minute: u8, second: u8) -> IResult<Self> {
        if hour > 23 {
            return Err("SecsOneDay::from_hms: hour must be in [0, 23]")?;
        }
        if minute > 59 {
            return Err("SecsOneDay::from_hms: minute must be in [0, 59]")?;
        }
        if second > 59 {
            return Err("TSecsOneDay::from_hms: second must be in [0, 59]")?;
        }
        let secs = hour as i32 * SECS_PER_HOUR + minute as i32 * SECS_PER_MINUTE + second as i32;
        Ok(Self { secs })
    }
    /// unsafe function, we don't judge the value range all of hour, minute and second
    pub fn from_hms_unsafe(hour: u8, minute: u8, second: u8) -> Self {
        let hour = if hour > 23 {
            (hour / 24) as i32
        } else {
            hour as i32
        };
        let minute = if minute > 59 {
            (minute / 60) as i32
        } else {
            minute as i32
        };
        let second = if second > 59 {
            (second / 60) as i32
        } else {
            second as i32
        };
        let secs = hour * SECS_PER_HOUR + minute * SECS_PER_MINUTE + second;
        Self { secs }
    }
    
    /// set second = 0(default),
    pub fn from_hm(hour: u8, minute: u8) -> IResult<Self> {
        if hour > 23 {
            return Err("SecsOneDay::from_hm: hour must be in [0, 23]")?;
        }
        if minute > 59 {
            return Err("SecsOneDay::from_hm: minute must be in [0, 59]")?;
        }
        let secs = hour as i32 * SECS_PER_HOUR + minute as i32 * SECS_PER_MINUTE;
        Ok(Self { secs })
    }
    
    /// unsafe function, we don't judge the value range both of hour and minute, we set second=0 defaultly
    pub fn from_hm_unsafe(hour: u8, minute: u8) -> Self {
        let hour = if hour > 23 {
            (hour / 24) as i32
        } else {
            hour as i32
        };
        let minute = if minute > 59 {
            (minute / 60) as i32
        } else {
            minute as i32
        };
        let secs = hour * SECS_PER_HOUR + minute * SECS_PER_MINUTE;
        Self { secs }
    }
    /// set minute = 0 and second = 0 defaultly.
    pub fn from_h(hour: u8) -> IResult<Self> {
        if hour > 23 {
            return Err("SecsOneDay::from_hm: hour must be in [0, 23]")?;
        }
        let secs = hour as i32 * SECS_PER_HOUR;
        Ok(Self { secs })
    }
    /// unsafe function, we don't judge the value range of hour, we set minute=0, second=0 defaultly.
    pub fn from_h_unsafe(hour: u8) -> Self {
        let hour = if hour > 23 {
            (hour / 24) as i32
        } else {
            hour as i32
        };
        let secs = hour * SECS_PER_HOUR;
        Self { secs }
    }
}



impl SecsOneDay {
    pub fn from_hms_friendly_unsafe(hhmmss: i32) -> Self {
        let (hour, mmss) = u2_int::divide_remain_i32(hhmmss, 1_0000);
        let (_days, hour) = u2_int::divide_remain_i32(hour, 24);

        let (minute, second) = u2_int::divide_remain_i32(mmss, 100);
        
        let secs = hour * SECS_PER_HOUR + minute * SECS_PER_MINUTE + second;
        Self { secs }
    }

}



impl SecsOneDay {
    pub fn secs_from_0_oclock(&self) -> i32 {
        self.secs
    }
    pub fn hms_i32(&self) -> (i32, i32, i32) {
        let (hour_minute, second) = u2_int::divide_remain_i32(self.secs, SECS_PER_MINUTE);
        let (hour, minute) = u2_int::divide_remain_i32(hour_minute, MINUTE_PER_HOUR);
        (hour, minute, second)
    }
    pub fn hms(&self) -> (u8, u8, u8) {
        let (hour, minute, second) = self.hms_i32();
        (hour as u8, minute as u8, second as u8)
    }
    pub fn hour(&self) -> u8 {
        (self.secs / SECS_PER_HOUR) as u8
    }
    pub fn minute(&self) -> u8 {
        let hour_minute = self.secs / SECS_PER_MINUTE;
        (hour_minute % MINUTE_PER_HOUR) as u8
    }
    pub fn second(&self) -> u8 {
        (self.secs % SECS_PER_MINUTE) as u8
    }


    /// i32 value of hms in friendly format: 10:23:45 ==> 102345
    pub fn hms_friendly(&self) -> i32 {
        let (hour, minute, second) = self.hms_i32();
        hour * 100_00 + minute * 100 + second
    }
    /// i32 value of hms in friendly format: 10:23:45 ==> 102345
    pub fn hm_friendly(&self) -> i32 {
        let (hour, minute, _) = self.hms_i32();
        hour * 100 + minute
    }
    pub fn to_print(&self) -> String {
        let (hour, minute, second) = self.hms_i32();
        format!("{hour:0>2}:{minute:0>2}:{second:0>2}")
    }
}
impl SecsOneDay {
    pub fn seconds_from(&self, tm_before: &Self) -> i32 {
        self.secs - tm_before.secs
    }
}

impl Debug for SecsOneDay {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SecsOneDay").field("print", &self.to_print()).field("inner", &self.secs).finish()
    }
}

impl Display for SecsOneDay {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.to_print())
    }
}

impl SecsOneDay {
    #[cfg(test)]
    pub(crate) fn test_generation_print() -> String {
        println!("SecsOneDay::test_generation_print: you should make sure that all assert! codes is passed");
        let mut buf = format!("SecsOneDay::test_generation_print: inner secs range from {SECS_MIN} to {SECS_MAX}:\n", );
        let mut hour_current = 0;
        let mut minute_current = 0;
        let mut second_current = 0;
        for secs in SECS_MIN..(SECS_MAX + 1) {
            let secs_oneday = Self { secs };
            let (hour, minute, second) = secs_oneday.hms();
            assert!(hour == hour_current && minute == minute_current && second == second_current);
            second_current += 1;
            if second_current == 60 {
                second_current = 0;
                minute_current += 1;
                if minute_current == 60 {
                    minute_current = 0;
                    hour_current += 1;
                }
            }
            buf.push_str(&format!("\t{secs:0>5}\t{}\n", secs_oneday.to_print()));
        }
        assert!(24 == hour_current && 0 == minute_current && 0 == second_current);

        let mut secs_current = 0;
        buf.push_str("SecsOneDay::test_generation_print: hour range from 0 to 23, minute range from 0 to 59, second range from 0 to 59:\n");
        for hour in 0..(23 + 1) {
            for minute in 0..(59 + 1) {
                for second in 0..(59 + 1) {
                    let secs_oneday = SecsOneDay::from_hms_unsafe(hour, minute, second);
                    assert!(secs_oneday.secs_from_0_oclock() == secs_current);
                    secs_current += 1;
                    buf.push_str(&format!("\th={hour:0>2} & m={minute:0>2} & s={second:0>2} ==> {:0>5}\t{}\n", secs_oneday.secs_from_0_oclock(), secs_oneday.to_print()));
                }
            }
        }
        assert!(86400 == secs_current);



        buf
    }
}



